#!/usr/bin/env ruby

# Copyright (c) 2023-2024 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

include_relative 'common.irt'

scoped_macro(:full_unroll_small) do |el_size, src_data, dst_data, len_bytes|
    # copy small (<= 16 bytes)

    # the followring IfImm(Compare(var, const)) sequences are for
    # the lowering to be able to create direct comparison with a
    # constant w/o prior loading it to a register
    if el_size < 2
        IfImm(Compare(len_bytes, 2).LT.b).Imm(0).NE.b {
            # 1 byte
            tmp := LoadI(src_data).Imm(0).u8
            StoreI(dst_data, tmp).Imm(0).u8
            Goto(:End)
        }
    end
    if el_size < 4
        IfImm(Compare(len_bytes, 2).EQ.b).Imm(0).NE.b {
            # 2 bytes
            tmp := LoadI(src_data).Imm(0).u16
            StoreI(dst_data, tmp).Imm(0).u16
            Goto(:End)
        }

        IfImm(Compare(len_bytes, 4).LT.b).Imm(0).NE.b {
            # 3 bytes
            tmp2 := LoadI(src_data).Imm(0).u16
            tmp1 := LoadI(src_data).Imm(2).u8
            StoreI(dst_data, tmp2).Imm(0).u16
            StoreI(dst_data, tmp1).Imm(2).u8
            Goto(:End)
        }
    end
    if el_size < 8
        IfImm(Compare(len_bytes, 4).EQ.b).Imm(0).NE.b {
            # 4 bytes
            tmp := LoadI(src_data).Imm(0).u32
            StoreI(dst_data, tmp).Imm(0).u32
            Goto(:End)
        }

        IfImm(Compare(len_bytes, 8).LT.b).Imm(0).NE.b {
            # 5-7 bytes
            src_right := Add(src_data, Cast(len_bytes).word).ptr
            dst_right := Add(dst_data, Cast(len_bytes).word).ptr

            tmp_left4 := LoadI(src_data).Imm(0).u32
            tmp_right4 := LoadI(src_right).Imm(-4).u32
            StoreI(dst_data, tmp_left4).Imm(0).u32
            StoreI(dst_right, tmp_right4).Imm(-4).u32
            Goto(:End)
        }
    end

    # 8-16 bytes
    tmp1 := LoadI(src_data).Imm(0).u64
    IfImm(Compare(len_bytes, 8).EQ.b).Imm(0).NE.b {
        Goto(:Tail)
    }
    tmp2 := Load(src_data, SubI(len_bytes).Imm(8).u32).u64
    Store(dst_data, SubI(len_bytes).Imm(8).u32, tmp2).u64
Label(:Tail)    
    StoreI(dst_data, tmp1).Imm(0).u64

Label(:End)
end

scoped_macro(:do_array_copy) do |el_size, el_log_size, src_obj, dst_obj, src_start, dst_start, len|
    # if len == 0 then do nothing
    If(len, 0).EQ.Unlikely.b {
        Goto(:End)
    }

    src_from_bytes := ShlI(src_start).Imm(el_log_size).u32
    dst_from_bytes := ShlI(dst_start).Imm(el_log_size).u32

    src_data := AddI(Cast(src_obj).ptr).Imm(Constants::ARRAY_DATA_OFFSET).ptr
    src_data := Add(src_data, Cast(src_from_bytes).word).ptr

    dst_data := AddI(Cast(dst_obj).ptr).Imm(Constants::ARRAY_DATA_OFFSET).ptr
    dst_data := Add(dst_data, Cast(dst_from_bytes).word).ptr

    # if src_data == dst_data then do nothing
    If(src_data, dst_data).EQ.Unlikely.b {
        Goto(:End)
    }

    ### Start copy
    len_bytes := ShlI(len).Imm(el_log_size).u32 
    IfImm(Compare(len_bytes, 16).GT.b).Imm(0).NE.Unlikely.b {
        LiveOut(dst_data).DstReg(regmap[:arg0]).ptr
        LiveOut(src_data).DstReg(regmap[:arg1]).ptr
        LiveOut(len_bytes).DstReg(regmap[:arg2]).u32
        ep_offset = get_entrypoint_offset("ARRAYCOPY_BIGLOOP")
        Intrinsic(:TAIL_CALL).AddImm(ep_offset).MethodAsImm("TryBigCopy").Terminator.void
        Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
    }
    full_unroll_small(el_size, src_data, dst_data, len_bytes)
Label(:End)
end
